/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
License for more details.

You should have received a copy of the GNU Lesser General Public
License along with GIMnet. (See COPYING.LESSER) If not, see
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Example client for the Image interface.
 * \author Antti Maula <antti.maula@tkk.fi>
 *
 * $Id: ImageClientExample.cpp,v 1.5 2009-10-13 06:56:38 ttaipalu Exp $
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string>
#include "owndebug.h"
#include "ownutils.h"
#include "binbag.h"
#include "ImageClient.hpp"
#include "ImageData.hpp"
#include "ImageContainer.hpp"
#define _USE_SDL_ 1
#ifdef _USE_SDL_
#warning "Using SDL - To disable SDL, don't compile with _USE_SDL_ macro defined."
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_thread.h>
#include <SDL/SDL_gfxPrimitives.h>

SDL_Surface *screen = NULL;
const int window_width = 640;
const int window_height = 480;
#endif

#ifdef _USE_GLUT_
#warning "Using glutImageGUI - To disable it, don't compile with _USE_GLUT_ macro defined."
#include "glutImageGUI.h"   // For visualisation
#endif
//*****************************************************************************
// Include GIMI in proper way
#define GIMI_CLIENT_API (20000)
#include "gimi.h"
//*****************************************************************************
// Set initial verbosity level to 1
static int verbose = 1;
//*****************************************************************************
// Choose your namespace
using namespace MaCI;
//*****************************************************************************
// Running?
static gimi::GIMI gg;
static volatile bool run = true;
static ownThreadHandle signalHandlerThread;
//*****************************************************************************
// Own.
static std::string imageFileBase("img");  ///< Image filename base.
static std::string datasource;
static std::string datasourceMaCISL;
static bool storeImages = true;
static unsigned int giveup_waiting_timeout_ms = 120000;
static int imageInterval = 1;

//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

static int SignalHandlerThread(void *)
{
  dPrint(8, "Signal handler thread (%ld) starting...", pthread_self());

  while (run) {
    // Now, wait for requested signals to arrive
    int sig;
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGPIPE);
    sigaddset(&mask, SIGHUP);
    sigwait(&mask, &sig);

    // Got signal, sigwait returned!
    dPrint(1,"Got Signal '%d' (%s)", sig, strsignal(sig));

    // What to do?
    switch(sig) {
    case SIGINT:
    case SIGTERM:
    case SIGPIPE:
    case SIGHUP:
      run = false;
      gg.stop();
      ownSleep_ms(1000);
      break;

    default:
      dPrint(1,"Unhandled signal! Ignore!");
      break;
    }
  }
  return 0;
}
//*****************************************************************************

static void InitSignalHandler(void)
{
  // Now; Set to BLOCK ALL SIGNALS
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGTERM);
  sigaddset(&mask, SIGPIPE);
  sigaddset(&mask, SIGHUP);
  pthread_sigmask(SIG_BLOCK, &mask, NULL);

  // Start signal handler thread
  signalHandlerThread = ownThread_Create((void*)SignalHandlerThread, NULL);
}
//*****************************************************************************
//*****************************************************************************

/**
 * All MaCI modules should support these parameters with exactly these names
 * *** DO NOT CHANGE NOR DELETE ANY OF THEM ***
 * Howevery, feel free to add new parameters as you wish.
 */
static void showUsage()
{
  fprintf(stderr,"\n");
  fprintf(stderr,"Usage:\tImageClientExample [OPTIONS]\n");
  fprintf(stderr,"  [-u addr]\t\t\tUse 'addr' as GIMnet AccessPoints address\n");
  fprintf(stderr,"  [-p port]\t\t\tUse 'port' as GIMnet AccessPoints port\n");
  fprintf(stderr,"  [-n name]\t\t\tUse 'name' as this nodes GIMnet name\n");
  fprintf(stderr,"  [-s name]\t\t\tSpecify the data source\n");
  fprintf(stderr,"  \t\t\t\tOR\n");
  fprintf(stderr,"  [-S name]\t\t\tSpecify the data source by MaCISL\n\n");
  fprintf(stderr,"  [-d]\t\t\t\tDon't store images to disk (doesn't save anything)\n");
  fprintf(stderr,"  [-l]\t\t\t\tMode selector; if this is set, the ImageClient will show available ImageSources and exits.\n");
  fprintf(stderr,"  [-i n]\t\t\tSave every n:th received image.\n");
  fprintf(stderr,"  [-b name]\t\t\tUse 'name' as base of stored images. Default is 'img'.\n\n");



  fprintf(stderr,"  [-v]\t\t\t\tIncrease verbosity by 1\n");
  fprintf(stderr,"  [-q]\t\t\t\tQuiet mode\n");
  fprintf(stderr, "\n\n");
}
//*****************************************************************************

static void WriteImageToFile(const std::string &aFilename, const Image::CImageContainer &aData)
{

  dPrint(4,"Attempting to write %u bytes of data to file '%s'",
         aData.GetImageDataSize(), aFilename.c_str());

  // Now go.
  FILE *f = fopen(aFilename.c_str(), "wb");
  if (f != NULL) {
    size_t elements_written = fwrite(aData.GetImageDataPtr(), aData.GetImageDataSize(), 1, f);
    if (elements_written != 1) {
      dPrintLE(1,"Warning: Failed to write to file '%s'!", aFilename.c_str());
    }
    fclose(f);
  } else {
    dPrintLE(1,"Warning: Failed to write to file '%s'!", aFilename.c_str());
  }
}
//*****************************************************************************

static void InitializeSDL(void)
{
#ifdef _USE_SDL_
  /////////////////////////////////////////////////////////////////////////////
  // Initialize SDL
  if(SDL_Init(SDL_INIT_VIDEO) == -1){
    dPrint(1,"Failed to initialize SDL Video: %s\n", SDL_GetError());
    exit(1);
  }

  // Register cleanup function for SDL
  atexit(SDL_Quit);

  // Initialize SDL-Video in mode WW*WH, 32bpp, & some flags.
  if ((screen = SDL_SetVideoMode(window_width, window_height, 32,
				 SDL_HWSURFACE|SDL_DOUBLEBUF|SDL_RESIZABLE)) == NULL) {
    dPrint(1, "Unable to set video mode for SDL Context: %s\n", SDL_GetError());
    abort();
  }

  // Clear the screen
  SDL_FillRect(screen, NULL, 0);

  // Set important info :)
  SDL_WM_SetCaption("ImageClientExample for MaCI::Image interface","ImageClientExample");

#endif // _USE_SDL_
}
//*****************************************************************************

static void ProcessSDL(void)
{
#ifdef _USE_SDL_

  // Now, run the loop of processing as long as the demo is alive.
  SDL_Event event;

  // Wait for incoming event
  while (SDL_PollEvent(&event)) {

    // Switch based on event type.
    switch (event.type) {

      // QUIT ('Window close' clicked)
    case SDL_QUIT: {
      // close button clicked
      dPrint(1,"'Window close' clicked.");
      run = false;
      break;
    }

      // Any key down
    case SDL_KEYDOWN: {
      // Switch based on the key that was pressed
      switch (event.key.keysym.sym) {

      case SDLK_q:
        dPrint(1,"'q' pressed.");
        run = false;
        break;

      default:
        // Default, print events
        dPrint(1,"SDL returned KEYDOWN event for '%s'" , SDL_GetKeyName(event.key.keysym.sym));
        break;
      }
      break; // SDL_KEYDOWN - end
    }

    case SDL_KEYUP: {
      switch (event.key.keysym.sym) {

      case SDLK_q:
        // No action for Command keys on UP.
        break;

      default:
        dPrint(1,"SDL returned KEYUP event for '%s'" , SDL_GetKeyName(event.key.keysym.sym));
        break;
      }
      break; // SDL_KEYUP - end
    }

      // Default handler. Nothing done.
    default:
      break; // default - end
    }
  }
#endif //_USE_SDL_
}
//*****************************************************************************

void DisplayImageSDL(MaCI::Image::CImageContainer &sid)
{
#ifdef _USE_SDL_

  // Convert to JPEG and back to RGB
  int r = sid.ConvertTo(MaCI::Image::KImageDataJPEG);
  if (r) r &= sid.ConvertTo(MaCI::Image::KImageDataRGB);
  if (r) r &= sid.ConvertTo(MaCI::Image::KImageDataJPEG);

  if (r) {
    SDL_Surface *image;
    SDL_RWops *rw;
    SDL_Rect rcDest = {0,0,0,0};

    rw = SDL_RWFromMem((void*)sid.GetImageDataPtr(),
                       sid.GetImageDataSize());

    image = IMG_LoadJPG_RW(rw);
    SDL_BlitSurface(image, NULL, screen, &rcDest);
    SDL_FreeSurface(image);

  } else {
      dPrint(1,"Converting Image to RGB failed, hence failing to Display!");

  }

  // Flip in every case!
  SDL_Flip(screen);

#endif //_USE_SDL_
}
//*****************************************************************************

void DisplayImageGLUT(MaCI::Image::CImageContainer &sid)
{
#ifdef _USE_GLUT_
  using namespace MaCI::Image;
  static bool isGLUTInitialized = false;
  const TImageInfo &info = sid.GetImageInfoRef();

  // Init GLUT funcs.
  if (isGLUTInitialized == false) {

    addImageWindow(info.imagewidth,
                   info.imageheight);
    glutImageGUI_Start();
    isGLUTInitialized = true;
  }

  if (sid.ConvertTo(KImageDataRGB)) {
    dPrint(1,"ImageSize is %u x %u",
           info.imagewidth,
           info.imageheight);
    // Display the image
    displayImage(0, const_cast<unsigned char*>(sid.GetImageDataPtr()), info.imageheight, info.imagewidth);

  } else {
    dPrint(1,"Failed to convert image to RGB!");

  }
#endif //_USE_GLUT_
}
//*****************************************************************************

static int ExecuteServiceLister(gimi::GIMI &g)
{
  int found = 0;
  using namespace MaCI::MaCICtrl;

  dPrint(1, "Searching for MaCI_Image services, please wait...");

  CMaCICtrlClient mcc(&g);
  gimi::t_serviceList servicelist;
  if (mcc.DiscoverMaCICtrlServices(servicelist) == KMaCIOK) {

    TServiceEntryArray sea;
    if (mcc.GetServiceArray(sea, servicelist) == KMaCIOK) {

      TServiceEntryArray s_images;
      if (mcc.FilterServices(s_images, sea, SMaCISL("*.MaCI_Image.*")) == KMaCIOK) {

        for(EACH_IN_i(s_images)) {
          if (i->service.type == KServiceTypeProvided) {
            dPrint(1,"ImageService at: '%s' (%08x) using minor '%d'; MaCISL: '%s'",
                   i->gimnetname.c_str(),
                   i->gimnetid,
                   i->service.serviceminor,
                   i->GetMaCISL().ToMaCISLString().c_str());
            ++found;
          }
        }
      }
    }
  }

  dPrint(1,"Search completed.");

  if (found == 0) {
    dPrint(1, "No MaCI_Image services were found, sorry.");
  }

  return 0;
}
//*****************************************************************************

static int ExecuteExampleClient(gimi::GIMI &g)
{
  using namespace MaCI::Image;
  CImageClient ic(&g, 0);
  CImageData id;
  CImageContainer sid;
  unsigned int id_seq = 0;
  char id_seq_string[32];
  bool r;
  // Init SDL (Will do nothing if SDL is not used)
  InitializeSDL();

  // Assign default target.
  if (datasource.size()) {
    ic.SetDefaultTarget(datasource);

  } else {
    // GIMI is Open(), so we can execute this.
    ic.SetDefaultTarget(MaCICtrl::SMaCISL(datasourceMaCISL));

  }

  // Open.
  if (ic.Open() == KMaCIOK) {

    dPrint(1,"Reading SourceInfoMap...");
    TSourceInfoMap sim;
    if (ic.GetSourceInfoMap(sim)) {
      dPrint(1,"Got %u entries in SourceInfoMap", sim.size());
      for(EACH_IN_i(sim)) {
        dPrint(1,"%u: '%s' - '%s'",
               i->second.sourceindex,
               i->second.sourcename,
               i->second.sourcedescription);
      }

      dPrint(1,"Reading Data for Index 0");
      TSourceInfo si;
      if (ic.GetSourceInfo(si, 0) == true) {
        dPrint(1,"Fetch function returned; %u: '%s' - '%s'",
               si.sourceindex,
               si.sourcename,
               si.sourcedescription);

      } else {
        dPrint(1,"Failed to fetch TSourceInfo entry with index 0!");

      }
    } else {
      dPrint(1, "WARNING: Failed to read SourceInfoMap from source!");

    }

    unsigned int getImage_time_since_last_update = 0;
    const unsigned int getImage_timeout_ms = 100;
    do {
      r = ic.GetImageData(id, &id_seq, getImage_timeout_ms);
      if (r) {
		  if (id_seq%imageInterval){
			continue;
		  }
        // Store to string
        sprintf(id_seq_string, "%07u", id_seq);

        // Debug & fetch first subimage
        dPrint(4,"Got CImageData!");
        r = id.GetImage(sid, NULL, false);
        if (r) {
          dPrint(6,"CImageData contained atleast one image -> Storing it!");

          // Select extension by type.
          const char *type =
            (sid.GetImageInfoRef().imagedatatype == KImageDataJPEG) ? ".jpg" :
            (sid.GetImageInfoRef().imagedatatype == KImageDataRGB) ? ".rgb" :
            (sid.GetImageInfoRef().imagedatatype == KImageDataPNG) ? ".png" : ".raw";

          // Write image.
          if (storeImages) WriteImageToFile(imageFileBase + "_" + id_seq_string + type, sid);

          // Convert to JPEG and write again (only if wasn't JPEG already)
          if (sid.GetImageInfoRef().imagedatatype != KImageDataJPEG) {

            // Convert to JPEG!
            r = sid.ConvertTo(KImageDataJPEG);
            if (r) {

              // Write again.
              type =
                (sid.GetImageInfoRef().imagedatatype == KImageDataJPEG) ? ".jpg" :
                (sid.GetImageInfoRef().imagedatatype == KImageDataRGB) ? ".rgb" :
                (sid.GetImageInfoRef().imagedatatype == KImageDataPNG) ? ".png" : ".raw";

              // Write image.
              if (storeImages) WriteImageToFile(imageFileBase + "_" + id_seq_string + type, sid);
            } else {
              dPrint(1,"Converting Image to JPEG failed, hence failing to Write.");
            }
          }
          // Call Display functions (they have content depending on the currently selected Macro)
          DisplayImageSDL(sid);
          DisplayImageGLUT(sid);

          // Reset timeout counter
          if (getImage_time_since_last_update) getImage_time_since_last_update = 0;

        } else {
          dPrint(1,"Warning: Received CImageData container didn't have any Images!");
          // Cumulating the timeout, as this also reflects an errorstate.
          getImage_time_since_last_update += getImage_timeout_ms;

        }

      } else {
        dPrint(8, "No imagedata available (timeout)");
        getImage_time_since_last_update += getImage_timeout_ms;

      }

      // Call SDL for input processing
      ProcessSDL();

      // Test for timeout
      if (giveup_waiting_timeout_ms > 0 &&
          getImage_time_since_last_update >= giveup_waiting_timeout_ms) {
        dPrint(1,"No new images in %u ms, giving up waiting!", giveup_waiting_timeout_ms);
        run = false;
      }

    } while(run);

  } else {
    dPrint(1,"Failed to Open() ImageClient.");

  }


  return 0;
}
//*****************************************************************************

int main(int argc, char *argv[])
{
  // GIMnet parameters
  std::string gimnetAP = "asrobo.hut.fi";
  int gimnetAPPort = 40002;
  std::string gimnetName;
  bool listServicesMode = false;

  // setup signal handling
  InitSignalHandler();

  // Objects
  // Init Debug lib
  debugInit();
  debugSetGlobalDebugLvl(4);
  const std::string logfile = std::string(argv[0]) + ".log";
  debugSetLogFilename(logfile.c_str());

  // Greet
  fprintf(stderr,
	  "\n%s - $Revision: 1.5 $\n", argv[0]);

  /////////////////////////////////////////////////////////////////////////////
  // Parse parameters - Remember: ** DO NOT MODIFY OR DELETE ANY OF THIS **
  // _Add_ anything as you wish.
  /////////////////////////////////////////////////////////////////////////////
  bool parse = true;
  while(parse) {
    int c;
    c = getopt(argc, argv, "b:n:u:p:s:S:i:ldhqv");
    switch(c) {

      /////////////////////////////////
      ////// Application specific /////
      /////////////////////////////////

    case 'l':
      dPrint(3,"Listing services.");
      listServicesMode = true; // No real modeparameter, as this is a test program only.
      break;

    case 's':
      datasource = optarg;
      dPrint(3,"Using DataSource '%s'", optarg);
      break;

    case 'S':
      datasourceMaCISL = optarg;
      dPrint(3,"Using DataSource MaCISL '%s'", optarg);
      break;

    case 'd':
      dPrint(3,"Enabled: Not storing images to disk.");
      storeImages = false;
      break;

    case 'b':
      imageFileBase = optarg;
      dPrint(3,"Using '%s' as basename for stored images", optarg);
      break;
      /////////////////////////////////////////////////////////////////////////
      /// Add here anything you need.
      /////////////////////////////////////////////////////////////////////////
    case 'i':
      imageInterval = atoi(optarg);
      dPrint(3,"Handling every %dth image", imageInterval);
      break;

      /////////////////////////////////
      ///// MaCI/GIMnet parameters ////
      /////////////////////////////////
    case 'n':
      gimnetName = optarg;
      dPrint(3,"Using GIMnet Name '%s'", optarg);
      break;

    case 'u':
      gimnetAP = optarg;
      dPrint(3,"Using GIMnetAP address '%s'", optarg);
      break;

    case 'p':
      gimnetAPPort = atoi(optarg);
      dPrint(3,"Using GIMnetAP port '%d'", gimnetAPPort);
      break;

      //////////////////////////////////
      //// GENERIC:                 ////
      //////////////////////////////////
    case 'v':
      // Increase only if not previously set to 0 (by 'quiet')
      if (verbose > 0)
	debugSetGlobalDebugLvl(++verbose);
      break;

    case 'q':
      verbose = 0;
      debugSetGlobalDebugLvl(0);
      break;

    case 'h':
    case '?':
      showUsage();
      exit(1);
      break;

    case -1:
      parse = false;
      break;
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // Execute connection to GIMnet.
  /////////////////////////////////////////////////////////////////////////////
  int r;
  if ( (r = gg.connectToHubEx(gimnetAP, gimnetAPPort, gimnetName) ) != GIMI_OK) {
    dPrint(1,"Failed to connect to GIMnet AccessPoint '%s:%d' with name '%s': '%s'",
	   gimnetAP.c_str(),
	   gimnetAPPort,
	   gimnetName.size()?gimnetName.c_str():"<anonymous>",
	   gimi::getGimiErrorString(r).c_str());

  } else {
    dPrint(2,"Succesfully connected to GIMnet at AccessPoint '%s:%d' with name '%s': '%s'",
	   gimnetAP.c_str(),
	   gimnetAPPort,
	   gimnetName.size()?gimnetName.c_str():"<anonymous>",
	   gimi::getGimiErrorString(r).c_str());

    if (listServicesMode) {
      r = ExecuteServiceLister(gg);

    } else {

      // GIMnet connection OK, now execute module.
      r = ExecuteExampleClient(gg);
    }
  }

  return 0;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************


